﻿using JESAI.DistributedLock.Internal;
using org.apache.zookeeper.data;
using System;
using System.Collections.Generic;

namespace JESAI.DistributedLock.ZooKeeper;

/// <summary>
/// 用于配置基于ZooKeeper的同步原语的选项
/// </summary>
public sealed class ZooKeeperDistributedSynchronizationOptionsBuilder
{
    private static readonly IReadOnlyList<ACL> DefaultAcl = new[] { ZooKeeperNodeCreator.PublicAcl };

    /// <summary>
    /// 根据 https://bowenli86.github.io/2016/09/15/distributed%20system/zookeeper/ZooKeeper-Sessions-and-Session-Management/，
    /// 超时时间可以是tick时间的最小2倍到最大20倍。默认的tick时间是2秒，因此此默认值设置得足够高，以需要相对较少的心跳次数，但又足够低以支持默认的2秒或1秒的tick时间。
    /// </summary>
    private TimeoutValue _sessionTimeout = TimeSpan.FromSeconds(20);

    /// <summary>
    /// 默认值（任意设定）与SQL Server的默认连接超时时间匹配
    /// </summary>
    private TimeoutValue _connectTimeout = TimeSpan.FromSeconds(15);

    private readonly List<ZooKeeperAuthInfo> _authInfo = new();
    private readonly List<ACL> _acl = new();

    private ZooKeeperDistributedSynchronizationOptionsBuilder()
    {
    }

    /// <summary>
    /// 配置与ZooKeeper的连接的<paramref name="sessionTimeout"/>。由于底层的ZooKeeper客户端会定期更新会话，此值通常不会影响行为。
    /// 较低的值意味着在持有锁的进程崩溃后，锁会更快地释放，但也增加了短暂的连接问题可能导致锁丢失的风险。
    /// 
    /// 默认值为20秒。
    /// </summary>
    public ZooKeeperDistributedSynchronizationOptionsBuilder SessionTimeout(TimeSpan sessionTimeout)
    {
        var sessionTimeoutValue = new TimeoutValue(sessionTimeout, nameof(sessionTimeout));
        if (sessionTimeoutValue.IsZero) { throw new ArgumentOutOfRangeException(nameof(sessionTimeout), "必须为正数"); }
        if (sessionTimeoutValue.IsInfinite) { throw new ArgumentOutOfRangeException(nameof(sessionTimeout), "不能为无限"); }

        this._sessionTimeout = sessionTimeoutValue;
        return this;
    }

    /// <summary>
    /// 配置与ZooKeeper建立连接之前等待的时间，超过该时间将抛出<see cref="TimeoutException"/>。
    /// 
    /// 默认值为15秒。
    /// </summary>
    public ZooKeeperDistributedSynchronizationOptionsBuilder ConnectTimeout(TimeSpan connectTimeout)
    {
        this._connectTimeout = new TimeoutValue(connectTimeout, nameof(connectTimeout));
        return this;
    }

    /// <summary>
    /// 指定要添加到ZooKeeper连接中的身份验证信息，通过<see cref="org.apache.zookeeper.ZooKeeper.addAuthInfo"/>。每次调用此方法都会向身份验证信息列表添加一个条目。
    /// 有关ZooKeeper身份验证的更多信息，请参阅 https://zookeeper.apache.org/doc/r3.5.4-beta/zookeeperProgrammers.html。
    /// 
    /// 默认情况下，不添加任何身份验证信息。
    /// </summary>
    public ZooKeeperDistributedSynchronizationOptionsBuilder AddAuthInfo(string scheme, IReadOnlyList<byte> auth)
    {
        this._authInfo.Add(new ZooKeeperAuthInfo(
            scheme ?? throw new ArgumentNullException(nameof(scheme)),
            new EquatableReadOnlyList<byte>(auth ?? throw new ArgumentNullException(nameof(auth)))
        ));
        return this;
    }

    /// <summary>
    /// 配置为创建的任何ZooKeeper节点设置访问控制列表(ACL)。每次调用此方法都会向访问控制列表添加一个条目。
    /// 有关ZooKeeper ACL的更多信息，请参阅 https://zookeeper.apache.org/doc/r3.5.4-beta/zookeeperProgrammers.html。
    /// 
    /// 如果未指定任何ACL条目，则使用的ACL将是一个单例列表，授予(world, anyone)所有权限。
    /// </summary>
    public ZooKeeperDistributedSynchronizationOptionsBuilder AddAccessControl(string scheme, string id, int permissionFlags)
    {
        this._acl.Add(new ACL(permissionFlags, new Id(scheme ?? throw new ArgumentNullException(nameof(scheme)), id ?? throw new ArgumentNullException(nameof(id)))));
        return this;
    }

    internal static (TimeoutValue SessionTimeout, TimeoutValue ConnectTimeout, EquatableReadOnlyList<ZooKeeperAuthInfo> AuthInfo, IReadOnlyList<ACL> Acl) GetOptions(Action<ZooKeeperDistributedSynchronizationOptionsBuilder>? options)
    {
        var builder = new ZooKeeperDistributedSynchronizationOptionsBuilder();
        options?.Invoke(builder);
        return (
            SessionTimeout: builder._sessionTimeout,
            ConnectTimeout: builder._connectTimeout,
            AuthInfo: new EquatableReadOnlyList<ZooKeeperAuthInfo>(builder._authInfo),
            Acl: builder._acl.Count != 0 ? builder._acl.ToArray() : DefaultAcl
        );
    }
}
